"""
A Python library for accessing, creating, and validating microformats.
"""
__author__ = 'Rob Ballou (rob.ballou@gmail.com)'
__version__ = '0.1a'
__license__ = 'New BSD'

import types
import inspect

__metaclass__ = type

#FORMAT_TYPES = ['hcard', 'hcalendar', 'hreview']
FORMAT_TYPES = ['hcard', 'hreview']

VALIDATOR_USER_AGENT = 'MicroformatValidator/%s +http://rballou.com/projects/python/microformats/' % __version__

# ERRORS
class MicroformatNotFoundError(Exception):
    pass


class UnknownMicroformatError(Exception):
    pass


class register_property(object):
    def __init__(self, mf=None):
        self.mf = mf
    
    def __call__(self, f):
        mod = self.mf
        if not mod:
            mod = inspect.getmodule(f).__name__
        MicroformatPropertyGenerator.register(mod, f.__name__, f)

class MicroformatPropertyGenerator(object):
    properties = {}
    
    def __init__(self, this_class, name):
        self.mf = this_class
        self.name = name
    
    def create(self):
        p = MicroformatPropertyGenerator.properties[self.mf][self.name](self.name)
        return p
    
    @classmethod
    def register(self, mf_class, name, f):
        # print "Register: %s.%s" % (mf_class, name)
        try:
            self.properties[mf_class][name] = f
        except KeyError, e:
            self.properties[mf_class] = {}
            MicroformatPropertyGenerator.register(mf_class, name, f)
            pass

# MicroformatProperty
class MicroformatProperty(object):
    """
    The property atom for microformat definitions
    """
    def __init__(self, name, required=False):
        self.name = name
        self.required = False
        self.cardinality = None
        self.value = None
        self.tag_rules = {}
        self.children = []
        self.parent = []
    
    def __repr__(self):
        return "%s = %s" % (self.name, self.value)
    
    def add_child(self, child):
        self.children.append(child)
    
    def get_name(self):
        return self.name
    
    def get_property(self, prop):
        for p in self.children:
            if p.name == prop:
                return p
    
    def has_children(self):
        return bool(len(self.children))
    
    def has_parent(self, property_type=None):
        if property_type == None:
            return len(self.parent) != 0
        
        for parent in self.parent:
            if parent.name == property_type:
                return True
        return False
    
    def has_property(self, prop):
        if self.name == prop: return True
        for p in self.children:
            if p.name == prop:
                return True
            if p.has_property(prop):
                return True
        return False
    
    def is_required(self, mf):
        return self.required
    
    def get_value(self):
        return self.value

# Microformat
class Microformat(object):
    """
    Parent "abstract" class for microformat implementations. This contains
    the basic functionality for all microformat definitions
    """
    
    name = ""
    schema = {'required': [], 'optional': []}
    
    def __init__(self):
        """
        Create a new instance of this microformat
        
        This automatically parses the schema dictionary and creates MicroformatProperty
        instances as necessary.
        """
        
        self.parser = None
        self.verbose = False
        
        # create the properties list
        self.properties = {'required':{}, 'optional':{}}
        
        # create the new schema with proper Properties
        if 'required' in self.schema:
            newSchema = {}
            for prop in self.schema['required']:
                (p, mf) = self.create_property_from_schema_node(prop)
                newSchema[p] = mf
            self.properties['required'] = newSchema
        
        if 'optional' in self.schema:
            newSchema = {}
            for prop in self.schema['optional']:
                (p, mf) = self.create_property_from_schema_node(prop)
                newSchema[p] = mf
            self.properties['optional'] = newSchema
    
    def __repr__(self):
        return self.name
    
    def count(self):
        return self.parser.count
    
    def create_property_from_schema_node(self, node):
        """
        Creates an instance of MicroformatProperty for this schema node.
        
        Arguments:
            node    The schema node
        
        Returns:
            A tuple of the microformat property's name and the microformat
        """
        # check if the node is actually a MicroformatProperty already
        if isinstance(node, MicroformatPropertyGenerator):
            return (str(node.name), node.create())
        
        tag_rules = None
        if type(node) is types.ListType:
            p = node[0]
            tag_rules = node[1]
        else:
            p = node
        mf = MicroformatProperty(p, required=True)
        if tag_rules != None:
            mf.tag_rules = tag_rules
        return (p, mf)
    
    def get_property(self, prop):
        """
        Get the value of the requested property
        """
        if prop in self.properties['required']:
            return self.properties['required'][prop]
        if prop in self.properties['optional']:
            return self.properties['optional'][prop]
        return None
    
    def get_schema(self):
        """
        Returns a single list of all schema properties associated with this microformat
        """
        s = self.properties['required']
        for prop in self.properties['optional']:
            s[prop] = self.properties['optional'][prop]
        return s
    
    def has_property(self, prop):
        for p in self.properties['required']:
            if self.properties['required'][p].name == prop and isinstance(self.properties['required'][p], MicroformatProperty): return True
        for p in self.properties['optional']:
            if self.properties['optional'][p].name == prop and isinstance(self.properties['optional'][p], MicroformatProperty): return True
        return False
    
    def parse(self, data):
        if self.verbose:
            self.parser.verbose = True
        self.parser.feed(data)
        self.parser.close()
        
        if self.parser.count == 0:
            raise MicroformatNotFoundError("No %s microformats could be found in this document" % self.name)
    
    def property_has_tag_rule(self, prop, tag):
        p = self.get_property(prop)
        if tag not in p.tag_rules:
            return False
        return True
    
    def property_uses_data(self, prop, tag):
        """
        Given the tag, does this property use data or an attribute
        """
        if self.property_has_tag_rule(prop, tag):
            return False
        return True
    
    def set_property(self, prop, value, parent=None):
        """
        Set the value of the requested property
        
        NOTE: There is a design flaw in this setup where it will only handle one child
        node correctly. Currently it is not clear that this will handle a triple-nested
        (or more) properties.
        """
        if self.verbose:
            print "set_property(%s, %s)" % (prop, value)
        self.set_property_attribute(prop, "value", value)
        if parent:
            for node in parent['node']:
                parent_property = self.get_property(node)
                parent_property.children.append(self.get_property(prop))
                self.get_property(prop).parent.append(self.get_property(node))
    
    def set_property_attribute(self, prop, attr, value):
        """
        Set the value for a property attribute
        """
        if prop in self.properties['required']:
            self.properties['required'][prop].__dict__[attr] = value
            return
        elif prop in self.properties['optional']:
            self.properties['optional'][prop].__dict__[attr] = value
            return
        raise KeyError("Could not find property %s" % prop)
    
